// OpentTxl-C Version 11 debugger
// J.R. Cordy, Jan 2023

// Copyright 2023, James R. Cordy and others

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the “Software”), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies 
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
// AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// The TXL command line interactive transformation debugger.
// Runs the user's TXL transformation step by step with tracing as directed from the command line.

// Modification Log

// v11.0 Initial revision, adapted from OpenTxl 11.0

#define XFORM

// I/O, strings, memory allocation
#include "support.h"

// Global modules
#include "locale.h"
#include "limits.h"
#include "options.h"
#include "tokens.h"
#include "errors.h"
#include "trees.h"
#include "shared.h"
#include "charset.h"
#include "idents.h"
#include "symbols.h"
#include "rules.h"

// Inter-phase dependencies
#include "xform.h"
#include "unparse.h"

// Check interface consistency
#include "debug.h"

// Global tracing controls
static int debugger_nsteps;             // 0
static bool debugger_matchfinding;      // false
static bool debugger_localtracing;      // false
static tokenT debugger_localrulename;
static tokenT debugger_matchrulename;
static int debugger_localdepth;         // 0

// Rules that we are breakpointing at
#define maxRuleBreakpoints 10
// 1-origin [1 .. maxRuleBreakpoints]
static tokenT debugger_breakpoints[maxRuleBreakpoints + 1];
static int debugger_nbreakpoints;       // 0

static void debugger_setbreakpoint (const tokenT ruleName)
{
    // Is it already set?
    for (int bp = 1; bp <= debugger_nbreakpoints; bp++) {
        if (debugger_breakpoints[bp] == ruleName) {
            fprintf (stderr, "  Breakpoint already set at rule %s\n", *ident_idents[ruleName]);
            return;
        }
    }

    // Are there too many?
    if (debugger_nbreakpoints >= maxRuleBreakpoints) {
        fprintf (stderr, "  ? Too many breakpoints\n");
        return;
    }

    // Add it
    debugger_nbreakpoints += 1;
    debugger_breakpoints[debugger_nbreakpoints] = ruleName;

    fprintf (stderr, "  Breakpoint set at rule %s\n", *ident_idents[ruleName]);
}

static void debugger_clearbreakpoint (const tokenT ruleName)
{
    // Is it set?
    int bp = 1;
    while (true) {
        if ((bp > debugger_nbreakpoints) || (debugger_breakpoints[bp] == ruleName)) 
            break;
        bp += 1;
    }

    if (bp >= debugger_nbreakpoints) {
        fprintf (stderr, "  ? Rule not being breakpointed\n");
        return;
    }

    // Remove it
    debugger_nbreakpoints -= 1;
    for (int b = bp; bp <= debugger_nbreakpoints; bp++) {
        debugger_breakpoints[b - 1] = debugger_breakpoints[b];
    }

    fprintf (stderr, "  Breakpoint cleared at rule %s\n", *ident_idents[ruleName]);
}

static bool debugger_isrealbreakpoint (const tokenT ruleName)
{
    // Is this rule an explicitly set breakpoint?
    for (int bp = 1; bp <= debugger_nbreakpoints; bp++) {
        if ((debugger_breakpoints[bp]) == ruleName) {
            return (true);
        }
    }
    return (false);
}

bool debugger_isbreakpoint (const tokenT ruleName)
{
    // Is this rule a breakpoint, either because it's explicitly set or because we're tracing it
    if (((debugger_nsteps > 0) || 
            (debugger_matchfinding 
                && ((debugger_matchrulename == ruleName) || (debugger_matchrulename == empty_T)))) 
            || (debugger_localtracing && (debugger_localrulename == ruleName))) {
        return (true);
    } else {
        return (debugger_isrealbreakpoint (ruleName));
    }
}

static void debugger_findruleordefine (const string sourceFileName, const string sourceDirectory, 
        const string rdId, string rdfilename, int *rdline)
{
    // NOTE: This routine needs to be reprogrammed to be more precise! - JRC

    // This is tricky, since we must find the LAST definition!
    // We do that by searching from the beginning of the spec to the end, exploring included files and 
    // setting the line and filename whenever we hit a definition.

    // Can we open the main source file?
    int sourceFile = 0;
    tfopen (OPEN_CHAR_READ, sourceFileName, &sourceFile);

    if (sourceFile == 0) {
        fprintf (stderr, "  ? Unable to open %s\n", sourceFileName);
        return;
    }

    // OK, try to find the rule or define in it
    int sourceLine = 0;

    while (true) {
        if (tfeof (sourceFile)) break;

        string line;
        tfgetstring (line, sourceFile);
        sourceLine += 1;

        const int ruleFunctionDefineIndex = stringindex (line, "rule") + stringindex (line, "function") + stringindex (line, "define");
        const int includeIndex = stringindex (line, "include");

        // Is this the one?
        if ((ruleFunctionDefineIndex != 0) && (stringindex (line, rdId) > (ruleFunctionDefineIndex + 1))) {
            const int restOfLineIndex = stringindex (line, rdId) + stringlen (rdId);
            string restOfLine;
            substring (restOfLine, line, restOfLineIndex, stringlen (line));
            const bool foundit = ((stringchar (restOfLine, 1) == EOS) || (stringchar (restOfLine, 1) == ' ') || (stringchar (restOfLine, 1) == '\t'));
            if (foundit) {
                stringcpy (rdfilename, sourceFileName);
                *rdline = sourceLine;
            }

        // If not, is this an include statement?
        } else if ((includeIndex != 0) && (stringindex (line, "\"") > (includeIndex + 1))) {
            // If so, can we open the included file?
            string includeFileName;
            stringcpy (includeFileName, line);
            substring (includeFileName, includeFileName, stringindex (includeFileName, "\"") + 1, stringlen (includeFileName));
            string temp;
            substring (temp, includeFileName, 1, stringindex (includeFileName, "\"") - 1);
            stringcpy (includeFileName, sourceDirectory), stringcat (includeFileName, temp);

            string includeDirectory;
            stringcpy (includeDirectory, "");

            if (stringindex (sourceFileName, "/") != 0) {
                stringcpy (includeDirectory, includeFileName);
                while (true) {
                    if (stringchar (includeDirectory, stringlen (includeDirectory)) == stringchar (directoryChar, 1)) break;
                    substring (includeDirectory, includeDirectory, 1, stringlen (includeDirectory) - 1);
                }
            }

            debugger_findruleordefine (includeFileName, includeDirectory, rdId, rdfilename, &(*rdline));
        }
    }

    tfclose (sourceFile);
}

static void debugger_showruleordefine (const string rdId)
{
    // Find and show the source of a rule or define in the TXL program 
    string sourceFileName;
    stringcpy (sourceFileName, options_txlSourceFileName);
    const int sourceFileNameLength = stringlen (sourceFileName);

    if (sourceFileNameLength > 4) {
        string ctxlend;
        substring (ctxlend, sourceFileName, sourceFileNameLength - 4, stringlen (sourceFileName));

        if (stringcmp (ctxlend, ".ctxl") == 0) {
            substring (sourceFileName, sourceFileName, 1, sourceFileNameLength - 5);
            stringcat (sourceFileName, ".txl");
            int sourceFile = 0;
            tfopen (OPEN_CHAR_READ, sourceFileName, &sourceFile);
            if (sourceFile == 0) {
                substring (sourceFileName, sourceFileName, 1, sourceFileNameLength - 4);
                stringcat (sourceFileName, ".Txl");
            } else {
                tfclose (sourceFile);
            }
        }
    }

    string sourceDirectory;
    stringcpy (sourceDirectory, "");

    if (stringindex (sourceFileName, "/") != 0) {
        stringcpy (sourceDirectory, sourceFileName);
        while (true) {
            if (stringchar (sourceDirectory, stringlen (sourceDirectory)) == stringchar (directoryChar, 1)) break;
            substring (sourceDirectory, sourceDirectory, 1, stringlen (sourceDirectory) - 1);
        }
    }

    // find the definition of the rule or define
    string rdfilename;
    stringcpy (rdfilename, "");
    int rdline = 0;

    debugger_findruleordefine (sourceFileName, sourceDirectory, rdId, rdfilename, &rdline);

    // OK, now rdfile and rdline must be the definition
    if (rdline == 0) {
        fprintf (stderr, "  ? Couldn't find rule or define of that name");

    } else {
        int rdfile;
        tfopen (OPEN_CHAR_READ, rdfilename, &rdfile);

        string line;
        for (int ln = 1; ln <= rdline; ln++) {
            tfgetstring (line, rdfile);
        }

        while (true) {
            fprintf (stderr, "%s\n", line);
            if (tfeof (rdfile) 
                    || (stringindex (line, "end define") != 0)
                    || (stringindex (line, "end redefine") != 0) 
                    || (stringindex (line, "end rule") != 0) 
                    || (stringindex (line, "end function") != 0)) {
                break;
            }
            tfgetstring (line, rdfile);
        }

        tfclose (rdfile);
    }
}

static void debugger_dbhelp (void) {
    fprintf (stderr, "\n");
    fprintf (stderr, "        TXL Debugger Commands\n");
    fprintf (stderr, "\n");
    fprintf (stderr, "  rules             list names of all rules \n");
    fprintf (stderr, "  rule              list name of current rule\n");
    fprintf (stderr, "  set/clr [RULE]    set/clear breakpoint at 'RULE' (default current)\n");
    fprintf (stderr, "  showbps           list names of all rule breakpoints\n");
    fprintf (stderr, "  scope             print current scope of application\n");
    fprintf (stderr, "  match[context]    print current pattern match (with or without scope context)\n");
    fprintf (stderr, "  result            print result of current replacement or rule\n");
    fprintf (stderr, "  vars              list names of all current visible TXL variables\n");
    fprintf (stderr, "  VAR or 'VAR       print current binding of TXL variable 'VAR'\n");
    fprintf (stderr, "  tree VAR          print tree of current binding of variable 'VAR'\n");
    fprintf (stderr, "  where             print current rule name and execution state\n");
    fprintf (stderr, "  show [RULDEF]     print source of rule/define 'RULDEF' (default current)\n");
    fprintf (stderr, "  run               continue execution until next breakpoint\n");
    fprintf (stderr, "  next or .         continue execution until next statement of current rule\n");
    fprintf (stderr, "  /[RULE]           continue until next pattern match of 'RULE' (default current)\n");
    fprintf (stderr, "  //                continue until next pattern match of any rule\n");
    fprintf (stderr, "  step [N] or <CR>  step trace execution for N (default 1) steps\n");
    fprintf (stderr, "  in RULE[-N] CMD   execute debugger command 'CMD' in context of rule 'RULE'\n");
    fprintf (stderr, "  help              print out this help summary\n");
    fprintf (stderr, "  quit              exit TXL\n");
}

// "Darren" mode provides auto-tracing of the entire transformation,
// dumping complete state information at every step, invoked using the "-Z" command line option
// Designed to support Darren Cousineau's obsolete windowed interactive debugging application,
// but still useful for debugging complex transformations by hand.

#define maxDarrenCommands 5
typedef char string12[13];
typedef string12 debugger_darrenCommandsT[maxDarrenCommands + 1];
static const debugger_darrenCommandsT debugger_darrenCommands[nDebuggerBreakpoints] = 
    {
        // startup
        {"", "step", "", "", "", ""},
        // shutdown
        {"", "step", "", "", "", ""},
        // ruleEntry
        {"", "vars", "scope", "tree scope", "%vars", "step"},
        // ruleExit
        {"", "step", "", "", "", ""},
        // matchEntry
        {"", "match", "tree match", "%vars", "step", ""},
        // matchExit
        {"", "result", "tree result", "step", "", ""},
        // deconstructExit
        {"", "%vars", "step", "", "", ""},
        // constructEntry
        {"", "step", "", "", "", ""},
        // constructExit
        {"", "%vars", "step", "", "", ""},
        // conditionExit
        {"", "step", "", "", "", ""},
        // importExit
        {"", "step", "", "", "", ""},
        // exportEntry
        {"", "step", "", "", "", ""},
        // exportExit
        {"", "step", "", "", "", ""},
        // historyCommand
        {"", "step", "", "", "", ""}
    };
static int debugger_darrenIndex;        // 0
static int debugger_darrenLocal;        // 0
static int debugger_darrenLastLocal;    // 0
static bool debugger_darrenTree;        // false

// Command buffer for looking into history
static string debugger_historyCommand;  // ""

void debugger_breakpoint (const enum debugger_breakpointT kind, const tokenT ruleName, const int partRef, const treePT scope, 
    const struct transformer_ruleEnvironmentT *ruleEnvironment, const bool success)
{
    const struct ruleLocalsT *localVars = (ruleEnvironment->localsListAddr);

    if ((kind == debug_startup) || (kind == debug_shutdown)) {
        // initializing or exiting

    } else if (((kind == debug_constructEntry) || (kind == debug_constructExit)) 
                && (stringchar (*ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], 1) == '_')) {
            // internal anonymous construct
            return;
    
    } else if (debugger_localtracing) {
        // we're waiting to get back to the interesting rule
        if (ruleName != debugger_localrulename) {
            return;
        } else {
            if (kind == debug_ruleEntry) {
                debugger_localdepth += 1;
            } else if (kind == debug_ruleExit) {
                debugger_localdepth -= 1;
            }
            if (debugger_localdepth == 0) {
                debugger_localtracing = false;
            } else {
                return;
            }
        }

    } else if (debugger_matchfinding) {
        // we're searching for a rule match
        if (!((kind == debug_matchEntry) 
                && ((ruleName == debugger_matchrulename) || (debugger_matchrulename == empty_T)))) {
            return;
        }
    }

    switch (kind) {
        case debug_startup:
            {
                debugger_dbhelp ();
            }
            break;

        case debug_shutdown:
            {
                fprintf (stderr, "  Exiting TXL program\n");
            }
            break;

        case debug_ruleEntry:
            {
                if (debugger_isrealbreakpoint (ruleName)) {
                    fprintf (stderr, "  >> Breakpoint\n");
                    debugger_nsteps = 0;
                    debugger_matchfinding = false;
                }

                fprintf (stderr, "  Applying rule %s\n", *ident_idents[ruleName]);

                if (options_option[darren_p]) {
                    debugger_darrenLocal = 1;
                    debugger_darrenLastLocal = localVars->nformals;
                }
            }
            break;

        case debug_ruleExit:
            {
                fprintf (stderr, "  Exiting rule %s", *ident_idents[ruleName]);

                if (success) {
                    fprintf (stderr, " (succeeded)\n");
                } else {
                    fprintf (stderr, " (failed)\n");
                }
            }
            break;

        case debug_matchEntry:
            {
                fprintf (stderr, "  Matched main pattern of rule %s\n", *ident_idents[ruleName]);

                if (options_option[darren_p]) {
                    debugger_darrenLocal = (localVars->nformals) + 1;
                    debugger_darrenLastLocal = localVars->nformals;
                    while (true) {
                        if ((debugger_darrenLastLocal == localVars->nlocals) 
                                || ((transformer_valueTP[ruleEnvironment->valuesBase + debugger_darrenLastLocal + 1]) == nilTree))
                            break;
                        debugger_darrenLastLocal += 1;
                    }
                }
            }
            break;

        case debug_matchExit:
            {
                fprintf (stderr, "  Done replacement of pattern match of rule %s\n", *ident_idents[ruleName]);
            }
            break;

        case debug_constructEntry:
            {
                fprintf (stderr, "  Entering construct of %s, in rule %s\n", 
                    *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);
            }
            break;

        case debug_constructExit:
            {
                fprintf (stderr, "  Exiting construct of %s, in rule %s\n", 
                    *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);

                if (options_option[darren_p]) {
                    debugger_darrenLocal = partRef;
                    debugger_darrenLastLocal = partRef;
                }
            }
            break;

        case debug_deconstructExit:
            {
                fprintf (stderr, "  Exiting deconstruct of %s, in rule %s", 
                    *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);

                if (success) {
                    fprintf (stderr, " (succeeded)\n");
                } else {
                    fprintf (stderr, " (failed)\n");
                }

                if (options_option[darren_p]) {
                    while (true) {
                        if ((debugger_darrenLastLocal == localVars->nlocals) 
                                || ((transformer_valueTP[ruleEnvironment->valuesBase + debugger_darrenLastLocal + 1]) == nilTree)) 
                            break;
                        debugger_darrenLastLocal += 1;
                    }
                }
            }
            break;

        case debug_conditionExit:
            {
                fprintf (stderr, "  Exiting where condition on %s, in rule %s", 
                    *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);

                if (success) {
                    fprintf (stderr, " (succeeded)\n");
                } else {
                    fprintf (stderr, " (failed)\n");
                }

                if (options_option[darren_p]) {
                    debugger_darrenLastLocal = localVars->nformals + 1;
                    while (true) {
                        if ((debugger_darrenLastLocal == localVars->nlocals) 
                                || ((transformer_valueTP[ruleEnvironment->valuesBase + debugger_darrenLastLocal + 1]) == nilTree)) 
                            break;
                        debugger_darrenLastLocal += 1;
                    }
                    debugger_darrenLocal = debugger_darrenLastLocal + 1;
                }
            }
            break;

        case debug_importExit:
            {
                fprintf (stderr, "  Exiting import of %s, in rule %s",
                    *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);

                if (success) {
                    fprintf (stderr, " (succeeded)\n");
                } else {
                    fprintf (stderr, " (failed)\n");
                }
            }
            break;

        case debug_exportEntry:
            {
                fprintf (stderr, "  Entering export of %s, in rule %s\n",
                    *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);
            }
            break;

        case debug_exportExit:
            {
                fprintf (stderr, "  Exiting export of %s, in rule %s",
                    *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);

                if (success) {
                    fprintf (stderr, " (succeeded)\n");
                } else {
                    fprintf (stderr, " (failed)\n");
                }
            }
            break;

        case debug_history:
            // (don't say anything!)
            break;
    }

    if (debugger_nsteps > 0) {
        debugger_nsteps -= 1;
        if (debugger_nsteps > 0) {
            return;
        }
    }

    debugger_nsteps = 0;
    debugger_matchfinding = false;

    if (options_option[darren_p]) {
        debugger_darrenIndex = 1;
        debugger_darrenTree = false;
    }

    // Debugger command processor
    while (true) {
        if (kind != debug_history) {
            fprintf (stderr, "TXLDB >> ");
        }

        // Flush all output stringeams to keep synchronous
        tfflush ();

        // Get the next command
        string command;

        if (options_option[darren_p]) {
            stringcpy (command, debugger_darrenCommands[kind][debugger_darrenIndex]);

            if (stringcmp (command, "%vars") == 0) {
                while (true) {
                    if ((debugger_darrenLocal > debugger_darrenLastLocal) 
                            || (stringchar (*ident_idents[rule_ruleLocals[localVars->localsBase + debugger_darrenLocal].name], 1) != '_')) 
                        break;
                    debugger_darrenLocal += 1;
                }

                if (debugger_darrenLocal <= debugger_darrenLastLocal) {
                    if (debugger_darrenTree) {
                        stringprintf (command, "tree %s", *ident_idents[rule_ruleLocals[localVars->localsBase + debugger_darrenLocal].name]);
                        debugger_darrenTree = false;
                        debugger_darrenLocal += 1;
                    } else {
                        stringcpy (command, *ident_idents[rule_ruleLocals[localVars->localsBase + debugger_darrenLocal].name]);
                        debugger_darrenTree = true;
                    }
                } else {
                    debugger_darrenIndex += 1;
                    stringcpy (command, debugger_darrenCommands[kind][debugger_darrenIndex]);
                    debugger_darrenIndex += 1;
                    debugger_darrenTree = false;
                }
            } else {
                debugger_darrenIndex += 1;
            }

            fprintf (stderr, "%s\n", command);

        } else if (kind == debug_history) {
            if (stringcmp (debugger_historyCommand, "") == 0) break;
            stringcpy (command, debugger_historyCommand);
            stringcpy (debugger_historyCommand, "");

        } else if (tfeof (tfstdin)) {
                stringcpy (command, "quit");

        } else {
            tfgetstring (command, tfstdin);
        }

        // Process the command
        if (stringcmp (command, "rules") == 0) {
            int outlength = 0;
            for (int r = 1; r <= rule_nRules; r++) {
                if ((outlength + stringlen (*ident_idents[rule_rules[r].name]) + 2) > 78) {
                    fprintf (stderr, "\n");
                    outlength = 0;
                }

                fprintf (stderr, "  %s", *ident_idents[rule_rules[r].name]);
                outlength += stringlen (*ident_idents[rule_rules[r].name]) + 2;
            }
            fprintf (stderr, "\n");

        } else if (stringcmp (command, "rule") == 0) {
            fprintf (stderr, "  %s\n", *ident_idents[ruleName]);

        } else if ((stringcmp (command, "step") == 0) || (stringcmp (command, "") == 0) || (stringncmp (command, "step ", 5) == 0)) {
            // trace for one or more steps
            if (stringindex (command, " ") != 0) {
                substring (command, command, 6, stringlen (command));

                debugger_nsteps = 0;
                for (int i = 0; i <= 999999; i++) {
                    if ((command[i] < '0') || (command[i] > '9')) break;
                    debugger_nsteps = (debugger_nsteps * 10) + (command[i] - '0');
                }

                if (debugger_nsteps == 0) {
                    fprintf (stderr, "  ? Bad step count\n");

                } else {
                    break;
                }
            } else {
                debugger_nsteps = 1;
                break;
            }

        } else if ((stringcmp (command, "next") == 0) || (stringcmp (command, ".") == 0)) {
            debugger_localtracing = 1;
            debugger_localrulename = ruleName;
            debugger_localdepth = 0;
            break;

        } else if ((stringcmp (command, "run") == 0) || (stringcmp (command, "go") == 0) || (stringcmp (command, "continue") == 0)) {
            break;

        } else if (stringcmp (command, "//") == 0) {
            debugger_matchfinding = true;
            debugger_matchrulename = empty_T;
            break;

        } else if (stringncmp (command, "/", 1) == 0) {
            int setrulename = ruleName;
            if (stringlen (command) > 1) {
                string ruleId;
                substring (ruleId, command, 2, stringlen (command));
                setrulename = ident_lookup (ruleId);
            }

            bool found = false;
            for (int r = 1; r <= rule_nRules; r++) {
                if ((rule_rules[r].name) == setrulename) {
                    found = true;
                    debugger_matchfinding = 1;
                    debugger_matchrulename = setrulename;
                    break;
                } else if (r == rule_nRules) {
                    fprintf (stderr, "  ? No such rule\n");
                    break;
                }
            }

            if (found) break;

        } else if ((stringcmp (command, "set") == 0) || (stringncmp (command, "set ", 4) == 0)) {
            string ruleId;
            if (stringindex (command, " ") != 0) {
                substring (ruleId, command, 5, stringlen (command));
            } else {
                stringcpy (ruleId, *ident_idents[ruleName]);
            }

            const tokenT setRuleName = ident_lookup (ruleId);
            for (int r = 1; r <= rule_nRules; r++) {
                if ((rule_rules[r].name) == setRuleName) {
                    debugger_setbreakpoint (setRuleName);
                    break;
                } else if (r == rule_nRules) {
                    fprintf (stderr, "  ? No such rule\n");
                    break;
                }
            }

        } else if ((stringcmp (command, "clear") == 0) || (stringcmp (command, "clr") == 0) 
                || (stringncmp (command, "clear ", 6) == 0) || (stringncmp (command, "clr ", 4) == 0)) {
            string ruleId;
            const int spindex = stringindex (command, " ");
            if (spindex != 0) {
                substring (ruleId, command, spindex + 1, stringlen (command));
            } else {
                stringcpy (ruleId, *ident_idents[ruleName]);
            }

            const tokenT clearRuleName = ident_lookup (ruleId);
            for (int r = 1; r <= rule_nRules; r++) {
                if ((rule_rules[r].name) == clearRuleName) {
                    debugger_clearbreakpoint (clearRuleName);
                    break;
                } else if (r == rule_nRules) {
                    fprintf (stderr, "  ? No such rule\n");
                    break;
                }
            }

        } else if (stringcmp (command, "showbps") == 0) {
            int outlength = 0;
            for (int bp = 1; bp <= debugger_nbreakpoints; bp++) {
                if ((outlength + stringlen (*ident_idents[debugger_breakpoints[bp]]) + 2) > 78) {
                    fprintf (stderr, "\n");
                    outlength = 0;
                }

                fprintf (stderr, "  %s", *ident_idents[debugger_breakpoints[bp]]);
                outlength += stringlen (*ident_idents[debugger_breakpoints[bp]]) + 2;
            }
            fprintf (stderr, "\n");

        } else if ((stringcmp (command, "show") == 0) || (stringncmp (command, "show ", 5) == 0)) {
            string ruleDefId;
            if (stringindex (command, " ") != 0) {
                substring (ruleDefId, command, 6, stringlen (command));
                while (true) {
                    if ((stringcmp (ruleDefId, "") == 0) || (stringchar (ruleDefId, 1) != ' ')) break;
                    substring (ruleDefId, ruleDefId, 2, stringlen (ruleDefId));
                }
            } else {
                stringcpy (ruleDefId, *ident_idents[ruleName]);
            }
            debugger_showruleordefine (ruleDefId);

        } else if ((stringcmp (command, "scope") == 0) || (stringcmp (command, "tree scope") == 0)) {
            bool foundit = false;

            if ((options_option[darren_p]) && (transformer_callDepth > 1)) {
                const struct ruleLocalsT *prevLocalVars = (transformer_callEnvironment[transformer_callDepth - 1].localsListAddr);
                for (int prevlocal = 1; prevlocal <= prevLocalVars->nlocals; prevlocal++) {
                    if (((transformer_valueTP[transformer_callEnvironment[transformer_callDepth - 1].valuesBase + prevlocal]) == scope)
                            && (stringchar (*ident_idents[rule_ruleLocals[prevLocalVars->localsBase + prevlocal].name], 1) != '_')) {
                        fprintf (stderr, "  (= <%s>)\n", (*ident_idents[rule_ruleLocals[prevLocalVars->localsBase + prevlocal].name]));
                        foundit = true;
                        break;
                    }
                }
            }

            if (!foundit) {
                const bool treeWanted = stringncmp (command, "tree", 4) == 0;

                if (treeWanted) {
                    substring (command, command, stringindex (command, " ") + 1, stringlen (command));
                }

                if ((kind != debug_matchExit) && (kind != debug_ruleExit) && (kind != debug_shutdown)) {
                    if (treeWanted) {
                        unparser_printParse (ruleEnvironment->scopeTP, 0, 0);
                    } else {
                        unparser_printLeaves (ruleEnvironment->scopeTP, 0, false);
                    }
                    fprintf (stderr, "\n");
                } else {
                    fprintf (stderr, "  ? No scope in this context\n");
                }
            }

        } else if ((stringcmp (command, "match") == 0) || (stringcmp (command, "matchcontext") == 0) || (stringcmp (command, "tree match") == 0)) {

            if ((options_option[darren_p]) && (scope == ruleEnvironment->scopeTP)) {
                fprintf (stderr, "  (= <scope>)\n");

            } else {
                const bool treeWanted = stringncmp (command, "tree", 4) == 0;

                if (treeWanted) {
                    substring (command, command, stringindex (command, " ") + 1, stringlen (command));
                }

                if (kind == debug_matchEntry) {
                    if (treeWanted) {
                        unparser_printParse (scope, 0, 0);
                    } else {
                        if (stringcmp (command, "matchcontext") == 0) {
                            unparser_printMatch (ruleEnvironment->scopeTP, scope, 0, false);
                        } else {
                            unparser_printLeaves (scope, 0, false);
                        }
                    }
                    fprintf (stderr, "\n");
                } else {
                    fprintf (stderr, "  ? No match in this context\n");
                }
            }

        } else if ((stringcmp (command, "result") == 0) || (stringcmp (command, "tree result") == 0)) {


            if ((options_option[darren_p]) && (scope == ruleEnvironment->scopeTP)) {
                fprintf (stderr, "  (= <scope>)\n");

            } else {
                const bool treeWanted = stringncmp (command, "tree", 4) == 0;

                if (treeWanted) {
                    substring (command, command, stringindex (command, " ") + 1, stringlen (command));
                }

                if ((kind == debug_matchExit) || (kind == debug_ruleExit) || (kind == debug_constructExit) || (kind == debug_shutdown)) {
                    if (treeWanted) {
                        unparser_printParse (scope, 0, 0);
                    } else {
                        unparser_printLeaves (scope, 0, false);
                    }
                    fprintf (stderr, "\n");
                } else {
                    fprintf (stderr, "  ? No result in this context\n");
                }
            }

        } else if (stringcmp (command, "vars") == 0) {

            for (int localIndex = 1; localIndex <= localVars->nlocals; localIndex++) {
                if (stringchar (*ident_idents[rule_ruleLocals[localVars->localsBase + localIndex].name], 1) != '_') {
                    fprintf (stderr, "  %s [%s]", *ident_idents[rule_ruleLocals[localVars->localsBase + localIndex].name],
                        *ident_idents[rule_ruleLocals[localVars->localsBase + localIndex].typename_]);
                }
            }
            fprintf (stderr, "\n");

        } else if ((stringcmp (command, "state") == 0) || (stringcmp (command, "where") == 0)) {

            switch (kind) {
                case debug_startup:
                    {
                        fprintf (stderr, "  Applying main rule\n");
                    }
                    break;

                case debug_shutdown:
                    {
                        fprintf (stderr, "  Exiting main rule\n");
                    }
                    break;

                case debug_ruleEntry:
                    {
                        fprintf (stderr, "  Applying rule %s\n", *ident_idents[ruleName]);
                    }
                    break;

                case debug_ruleExit:
                    {
                        fprintf (stderr, "  Exiting rule %s", *ident_idents[ruleName]);
                        if (success) {
                            fprintf (stderr, " (succeeded)\n");
                        } else {
                            fprintf (stderr, " (failed)\n");
                        }
                    }
                    break;

                case debug_matchEntry:
                    {
                        fprintf (stderr, "  Matched pattern of rule %s\n", *ident_idents[ruleName]);
                    }
                    break;

                case debug_matchExit:
                    {
                        fprintf (stderr, "  Done replacement of pattern match of rule %s", *ident_idents[ruleName]);
                        if (success) {
                            fprintf (stderr, " (succeeded)\n");
                        } else {
                            fprintf (stderr, " (failed)\n");
                        }
                    }
                    break;

                case debug_constructEntry:
                    {
                        fprintf (stderr, "  Entering construct of %s in rule %s\n", 
                            *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);
                    }
                    break;

                case debug_constructExit:
                    {
                        fprintf (stderr, "  Exiting construct of %s in rule %s\n", 
                            *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);
                    }
                    break;

                case debug_deconstructExit:
                    {
                        fprintf (stderr, "  Exiting deconstruct of %s in rule %s", 
                            *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);
                        if (success) {
                            fprintf (stderr, " (succeeded)\n");
                        } else {
                            fprintf (stderr, " (failed)\n");
                        }
                    }
                    break;

                case debug_conditionExit:
                    {
                        fprintf (stderr, "  Exiting where condition on %s in rule %s", 
                            *ident_idents[rule_ruleLocals[localVars->localsBase + partRef].name], *ident_idents[ruleName]);
                        if (success) {
                            fprintf (stderr, " (succeeded)\n");
                        } else {
                            fprintf (stderr, " (failed)\n");
                        }
                    }
                    break;

                default:
                    assert (false);
            }

            for (int c = transformer_callDepth - 1; c >= 1; c--) {
                fprintf (stderr, "    called from %s\n", *ident_idents[transformer_callEnvironment[c].name]);
            }

        } else if ((stringcmp (command, "help") == 0) || (stringcmp (command, "?") == 0)) {
            debugger_dbhelp ();

        } else if ((stringcmp (command, "exit") == 0) || (stringcmp (command, "quit") == 0) || (stringcmp (command, "bye") == 0)) {
            fprintf (stderr, "  Exiting TXL\n");
            throw (QUIT);

        } else if (stringncmp (command, "in ", 3) == 0) {
            // perform a debugger command in a previous rule's context
            string ruleId;
            substring (ruleId, command, 4, stringlen (command));

            const int spindex = stringindex (ruleId, " ");
            if (spindex != 0) {
                substring (debugger_historyCommand, ruleId, spindex + 1, stringlen (ruleId));
                substring (ruleId, ruleId, 1, spindex - 1);

                const int minusindex = stringindex (ruleId, "-");
                int instance = 0;
                if (minusindex != 0) {
                    string instancestring;
                    substring (instancestring, ruleId, minusindex + 1, stringlen (ruleId));
                    substring (ruleId, ruleId, 1, minusindex - 1);

                    for (int i = 0; i <= 999999; i++) {
                        if ((instancestring[i] < '0') || (instancestring[i] > '9')) break;
                        instance = (instance * 10) + (instancestring[i] - '0');
                    }

                    if (instance == 0) {
                        fprintf (stderr, "  ? Bad instance number\n");
                    }
                }

                // find the previous rule's context and issue the command
                for (int c = transformer_callDepth; c >= 0; c--) {
                    if (stringcmp (*ident_idents[transformer_callEnvironment[c].name], ruleId) == 0) {
                        if (instance > 0) {
                            instance -= 1;
                        } else {
                            // recursive call, with previous context
                            debugger_breakpoint (debug_history, transformer_callEnvironment[c].name, 0, 
                                transformer_callEnvironment[c].scopeTP, &(transformer_callEnvironment[c]), success);
                            break;
                        }
                    } else if (c == 0) {
                        fprintf (stderr, "  ? No such rule in history\n");
                    }
                }
            }

        } else if (stringlen (command) > 0) {
            const bool treeWanted = stringncmp (command, "tree ", 5) == 0;

            if (treeWanted) {
                substring (command, command, stringindex (command, " ") + 1, stringlen (command));
            }

            if (stringchar (command, 1) == '\'') {
                substring (command, command, 2, stringlen (command));
            }

            const char c = stringchar (command, 1);

            if ((stringcmp (command, "") == 0) || (!(charset_alphaP[(unsigned char) c]))) {
                fprintf (stderr, "  ? bad command ('help' for list of commands)\n");

            } else {
                bool found = false;
                for (int localIndex = 1; localIndex <= localVars->nlocals; localIndex++) {
                    if (stringcmp (*ident_idents[rule_ruleLocals[localVars->localsBase + localIndex].name], command) == 0) {
                        if ((transformer_valueTP[ruleEnvironment->valuesBase + localIndex]) == 0) {
                            fprintf (stderr, "  ? unbound\n");
                        } else {
                            if ((options_option[darren_p]) 
                                    && (transformer_valueTP[ruleEnvironment->valuesBase + localIndex] == ruleEnvironment->scopeTP)) {
                                fprintf (stderr, "  (= <scope>)\n");
                            } else {
                                if (treeWanted) {
                                    unparser_printParse (transformer_valueTP[ruleEnvironment->valuesBase + localIndex], 0, 0);
                                } else {
                                    unparser_printLeaves (transformer_valueTP[ruleEnvironment->valuesBase + localIndex], 0, false);
                                }
                                fprintf (stderr, "\n");
                            }
                        }
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    fprintf (stderr, "  ? No such variable in the context (or bad command)\n");
                }
            }

        } else {
            // normally can't get here, but what the hey ...
            fprintf (stderr, "  ? bad command ('help' for list of commands)\n");
        }
    }
}

// Initialization
void debugger_initializeDebugger (void) {
    debugger_nsteps = 0;
    debugger_matchfinding = false;
    debugger_localtracing = false;
    debugger_localdepth = 0;
    debugger_nbreakpoints = 0;
    debugger_darrenIndex = 0;
    debugger_darrenLocal = 0;
    debugger_darrenLastLocal = 0;
    debugger_darrenTree = false;
    stringcpy (debugger_historyCommand, "");
}
